Repository navigation
feat(quote): add QuoteTransport to serve quote pull APIs over HTTP - #604
Merged
Merged
Conversation
Add `Config::quote_transport(QuoteTransport::{WebSocket, Http})` (env
`LONGBRIDGE_QUOTE_TRANSPORT=ws|http`, default WebSocket). With `Http`, the
19 `QuoteContext` pull APIs that have a REST equivalent are sent as
`POST /quote/*` instead of over the quote WebSocket, so a process that only
pulls data never opens a WebSocket connection. Method signatures and return
types are unchanged: the request is the same prost message serialised as
JSON, and the gateway proto-JSON response is decoded back into the same
prost message by a lenient deserializer (int64-as-string, omitted/null
fields, camelCase keys), so the existing conversions are reused.
Results are aligned with the WebSocket, including US overnight data, which
the gateway always returns over REST but the WebSocket only returns when
`enable_overnight` is set: on the HTTP path the SDK strips
`overnight_quote`, filters overnight intraday lines, drops overnight
candlesticks and tops up count/range-capped candlestick windows with offset
queries (`quote::overnight`, unit-tested).
Expose the option in the C, C++, Java, Node.js and Python layers, mirroring
`PushCandlestickMode`, and add `examples/rust/quote_http_compare`, which
calls every pull API over both transports and diffs the results.
- refill: once a page yields no usable bar the cursor is inside the contiguous 20:00-04:00 overnight block; fetch a full page to jump it instead of inching through (regression tests for both directions) - gate overnight dropping on the same US-equity predicate everywhere and compute the window target in one place (CandlestickWindow / window_target, unit-tested) - CHANGELOG: split the entry, list the top-up endpoint, error-variant, 429-retry and overnight-session caveats; 20 methods / 19 commands - drop the undocumented "websocket" env alias; ignore only .env/.env.local; revert unrelated example reformatting
hogan-yuan
force-pushed
the
feat/quote-http-transport
branch
from
October 10, 2026 10:28
ef2a9d6 to
7a24f57
Compare
- only drop overnight candlesticks for TradeSessions::All queries, so a page is either left untouched or dropped and topped up (matches Go) - a date-range page shorter than the request cap is never treated as capped; avoids a wasted top-up request for weekend/holiday starts - warn when the top-up returns fewer candlesticks than the WebSocket would - clear overnight_quote only for US equities, like intraday/candlesticks
- send /quote/* requests under the quote context's log subscriber, like every other HTTP call, so request/response logging lands in the SDK log and not in the host process's global subscriber; log quote_transport - match the .US suffix case-insensitively when aligning overnight data - only warn about a short top-up for count windows; a date range stopping at its start is expected - doc/changelog: return types match, not every value; list the missing per-command rate limit, the up-to-9-request top-up and depth/capital_flow among the overnight-session caveats
- refill reports whether it stopped on MAX_REFILL_ROUNDS while still short; running out of data (e.g. a recent listing) no longer warns - mark quote::overnight as a stopgap for the missing REST overnight opt-out and list the server behaviour it relies on - tests: lock the JSON sent for a top-up offset query (New York date/minute, numeric enums, no date_request) and a few other request bodies - changelog: intraday can be empty over HTTP during the overnight session, trades may carry overnight ticks around it, no client-side rate limiting
hogan-yuan
added a commit
to longbridge/openapi-go
that referenced
this pull request
Oct 13, 2026
## What Ports longbridge/openapi#604 to the Go SDK. Adds `quote.Transport` (`quote.TransportWebSocket`, default / `quote.TransportHTTP`), set with `quote.WithTransport(...)`, `config.Config.QuoteTransport` or env `LONGBRIDGE_QUOTE_TRANSPORT=ws|http`. With `TransportHTTP` the 20 `QuoteContext` pull methods backed by the 19 WebSocket commands that have a REST equivalent (`StaticInfo`, `Quote`, `OptionQuote`, `WarrantQuote`, `Depth`, `Brokers`, `Participants`, `Trades`, `Intraday`, `Candlesticks`, `HistoryCandlesticksByOffset/ByDate`, `OptionChainExpiryDateList`, `WarrantIssuers`, `WarrantList`, `TradingSession`, `TradingDays`, `CapitalFlow`, `CapitalDistribution`, `CalcIndex`) go over `POST /quote/*` instead of the quote WebSocket. Method signatures and return types are unchanged. ## Why Same motivation as the Rust PR: longbridge/longbridge-mcp#162 re-implemented every quote tool on the REST endpoints inside the MCP to get rid of its per-user WebSocket pool. That belongs in the SDK, and Go consumers should get the same switch. ## Usage Opt in per context (default stays `TransportWebSocket`, so nothing changes for existing users). Either set the env var: ```bash export LONGBRIDGE_QUOTE_TRANSPORT=http # ws (default) | http ``` or set it in code. After that, call the quote APIs exactly as before — same methods, same return types: From config / env (recommended — the quote WebSocket URL follows the configured region): ```go cfg, err := config.New() if err != nil { return err } cfg.QuoteTransport = "http" // or LONGBRIDGE_QUOTE_TRANSPORT=http qctx, err := quote.NewFromCfg(cfg) // no WebSocket is dialed here with HTTP if err != nil { return err } defer qctx.Close() quotes, err := qctx.Quote(context.Background(), []string{"700.HK", "AAPL.US"}) // POST /quote/quotes ``` With options: ```go httpClient, err := http.NewFromCfg(cfg) if err != nil { return err } qctx, err := quote.New( quote.WithHttpClient(httpClient), quote.WithTransport(quote.TransportHTTP), quote.WithQuoteURL(cfg.QuoteUrl), // needed outside the global region if you will subscribe ) ``` Notes: - `Subscribe` / `Unsubscribe`, `Profile()` and APIs without a REST equivalent still use the WebSocket, which is then dialed on first use; `On*` callbacks registered before that are queued and attached when it happens. A context that only calls the pull APIs never opens it. After `Close` nothing re-dials (`quote.ErrClosed`). - `WithEnableOvernight` keeps its meaning on both transports (US overnight data is returned only when it is set). - Comparison tool: `cd examples && go run ./quote_http_compare` calls every pull API over both transports and prints both results with an `EQUAL` flag per call. ## How - `quote/transport.go`: `httpPaths` maps WebSocket commands to REST paths; `core.do` branches on the transport. Requests are the same protobuf messages encoded with `protojson` (proto field names, absent sub-messages omitted). Responses go through `normalizeProtoJSON`, a descriptor-driven pass that rewrites the gateway encodings protojson rejects (`""` on numeric/enum fields, quoted enum numbers, `"true"`/`"false"`/`1`/`0` on bools, numbers on string fields, `null` list elements), then `protojson` (`DiscardUnknown`, so the extra `volume_str` is ignored). A `null` `data` is an empty response. - **Lazy WebSocket**: `newCore` no longer dials unconditionally. With `TransportWebSocket` it dials and fetches the profile up front exactly as before; with `TransportHTTP` the socket is dialed on first need (`Subscribe`, `Profile()`, or any command without a REST path) via `core.ws()`, with a context detached from the caller's cancellation. Push handlers registered before the dial are queued and attached when it happens. A socket whose authentication fails is closed rather than leaked. After `Close()` nothing re-dials. `Subscribe` now returns the subscribe error (it was dropped before, which a lazy dial would have turned into silent failures). - `quote/overnight.go` aligns the one known server-side difference: REST always returns US overnight data and has no opt-out, while the WebSocket only returns it with `WithEnableOvernight(true)`. On the HTTP path (and only there) the SDK clears `OverNightQuote`, filters overnight `Intraday` lines for US equities, and for `CandlestickTradeSessionAll` history queries drops `OVERNIGHT_TRADE` bars and tops up count/range-capped windows with offset queries (≤1000 per request, ≤8 rounds). Changes visible on the default WebSocket transport (all listed in the CHANGELOG): `Subscribe` now returns the subscribe error instead of always `nil`; `Close` is idempotent (a second call used to panic) and final; the socket is closed if the initial profile query fails; `LONGBRIDGE_ENABLE_OVERNIGHT` is now read (users who set it per the README will start receiving US overnight data); the `Brokers` region-restriction error text is `quote/brokers`. `http.Client.Call` returns an empty `*json.RawMessage` instead of `EOF` for a JSON response without `data` (a non-JSON 200 is still an error). - `examples/quote_http_compare` calls every pull API over both transports and diffs the JSON output. ## Verification - `go test -race ./quote/` passes (new tests: refill paging incl. the overnight-block jump in both directions, `alignCandlesticks` / `windowTarget` early exits and capped/uncapped date ranges, US overnight window in EDT/EST, command→path map completeness, gateway proto-JSON decode replay and normalisation edge cases, request encoding). - New tests also cover `httpDo` end to end against `httptest` (request path/body, absent and `null` `data`, a non-JSON 200, API errors) and the closed-context lifecycle (never dialed and dialed-then-closed). The two failing `config` package tests (`Test_YamlConfig` / `Test_TomlConfig`) fail identically on `main` (missing fixture files) and are unrelated. - Follow-up commits address review (`WithTransport` validation, fail-fast `New` without an HTTP client, proto-JSON normalisation, `Subscribe` error, close/leak handling, date-range capped rule aligned with the Rust port). Please squash on merge. - `go vet ./quote/` reports only the pre-existing `copylocks` warnings from `util.Copy` (same 8 on `main`). - `examples/quote_http_compare` against a staging environment: 30/32 cases identical between transports with overnight both off and on; the 2 remaining are the gateway error-code gap below. Production: all successful cases structurally identical; remaining value-level differences come from inconsistent `/quote/*` replicas (see below). ## Known gateway gaps (not fixable in the SDK) 1. REST reports business errors as `500 internal server error` instead of passing the code through (WS: `301600`, `301604`, `301607`). 2. Production `/quote/*` replicas are not consistent with each other (value-level differences in `avg_price`/`volume`; two consecutive REST calls can disagree while the WS is stable). 3. No REST opt-out for US overnight data (handled client-side here as a stopgap; `quote/overnight.go` should be removed once the REST endpoints honour `need_over_night_quote`). Follow-up (not in this PR): a client-side rate limit for the HTTP path; reading `LONGBRIDGE_LANGUAGE` (same class of env-tag bug as `LONGBRIDGE_ENABLE_OVERNIGHT`). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Merged
hogan-yuan
added a commit
that referenced
this pull request
Oct 14, 2026
Release **v5.2.1**. Bumps workspace `5.2.0` → `5.2.1`. ### New since v5.2.0 - **Added:** `OrderStatus::DelayedNotReported` — monitoring, the state a time-condition order sits in before it is reported to the exchange; appended after `PartialWithdrawal` so C/C++/Node.js discriminants stay stable (#595) - **Added:** time-condition orders — `delayed_until` on submit / multi-leg submit (held in `DelayedNotReported` and sent at that time, ≤ 90 days ahead; multi-leg `LO` / `MO` only) and on replace (move the time, or `clear_delayed_until` to turn it back into a regular order); `Order` / `OrderDetail` gain `delayed_until`. Across Rust, C, C++, Java, Node.js and Python (the Go SDK ships separately, openapi-go#123) (#595) - **Added:** `QuoteTransport` config option to serve quote pull APIs over HTTP, exposed in every binding layer (#604) Only `Cargo.toml` (workspace version + internal dep reqs) and `CHANGELOG.md` change. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
Config::quote_transport(QuoteTransport::{WebSocket, Http})(envLONGBRIDGE_QUOTE_TRANSPORT=ws|http, defaultWebSocket). WithHttp, the 20QuoteContextpull methods backed by the 19 WebSocket commands that have a REST equivalent (static_info,quote,option_quote,warrant_quote,depth,brokers,participants,trades,intraday,candlesticks,history_candlesticks_by_offset/by_date,option_chain_expiry_date_list,warrant_issuers,warrant_list,trading_session,trading_days,capital_flow,capital_distribution,calc_indexes) go overPOST /quote/*instead of the quote WebSocket. A process that only pulls data never opens a WebSocket connection. Public method signatures and return types are unchanged.The option is exposed in all six layers (Rust, C, C++, Java, Node.js, Python), mirroring
PushCandlestickMode.Why
longbridge/longbridge-mcp#162 re-implemented every quote tool on top of the REST endpoints inside the MCP (per-tool JSON reshaping) to get rid of its per-user WebSocket pool. That logic belongs in the SDK: one switch in
QuoteContext::request, and every SDK consumer (MCP, CLI, …) gets it with identical types.Usage
Opt in per
Config(default staysWebSocket, so nothing changes for existing users). Either set the env var:or set it in code. After that, call the quote APIs exactly as before — same methods, same return types:
Rust
Python
Node.js
Java
C++
C
Notes:
realtime_*,member_id/quote_level/quote_package_detailsstill use the WebSocket, which is then connected on first use. A process that only calls the pull APIs never opens it.enable_overnightkeeps its meaning on both transports (US overnight data is returned only when it is set).cd examples/rust && cargo run -p quote_http_comparecalls every pull API over both transports and prints the diff.How
rust/src/quote/cmd_code.rs::http_pathmaps WS command codes to REST paths;QuoteContext::request/request_without_bodybranch on the transport. The request is the same prost message serialised as JSON (nulls stripped); the response is decoded into the same prost message byrust/src/quote/http_json.rs, a lenient serde deserializer (int64-as-string, omitted/null→ default, camelCase keys, unknown fields ignored), so all existingTryFromconversions are reused.rust/src/quote/overnight.rsaligns the one known server-side difference: REST always returns US overnight data and has no opt-out, while the WS only returns it whenenable_overnightis set. On the HTTP path (and only there) the SDK applies the same rule for US equities: clearsovernight_quote, filters overnight intraday lines and, forTradeSessions::Allcandlestick queries, dropsOvernightTradebars and tops up count/range-capped windows withPOST /quote/history-candlesticksoffset queries (≤1000 per request, ≤8 rounds; when a page yields nothing the cursor is inside the contiguous overnight block and the next page is a full 1000 to jump it; a shortfall is logged). The WebSocket path is functionally identical tomain(the only visible change is thebrokersregion-restriction error path,"quote/brokers (WebSocket)"→"quote/brokers"). HTTP-path requests are logged through the quote context's log subscriber like every other SDK HTTP call.examples/rust/quote_http_comparecalls every pull API over both transports and diffs the results.Verification
examples/rust/quote_http_comparewas run against a staging environment and production, with overnight on and off:/quote/*replicas serve different data).cargo test -p longbridge --lib quote::20/20 (refill paging incl. the overnight-block jump in both directions, window targets, US overnight window in EDT/EST, proto-JSON decode edge cases, request null-stripping and the JSON sent for top-up offset queries);cargo clippy --all --all-featuresadds no warnings;cargo +nightly fmt --all.TradeSessions::Allpredicate (suffix matched case-insensitively),window_target(a date-range page is capped only if it is full-size and does not reachstart), HTTP logging through the SDK log subscriber, a warning only when the top-up hits its round limit, CHANGELOG caveats (incl.intradaypossibly empty over HTTP during the overnight session) (top-up endpoint and request count, error variant, missing per-command rate limit, 429 retry, overnight-session values),"websocket"env alias removed. Please squash on merge.Known gateway gaps (not fixable in the SDK)
500 internal server errorinstead of passing the code through (WS:301600,301604,301607). Callers that branch on error codes will not work over HTTP until this is fixed./quote/*data is not consistent across replicas.quote::overnightshould be removed once the REST endpoints honourneed_over_night_quote).Follow-up (not in this PR): a client-side rate limit / jittered retry for the HTTP path, which today relies only on the HTTP client's
429back-off.🤖 Generated with Claude Code